#ifndef __INCLUDE_GUARD_8B7841BB_FFCF_42B9_BF24_2CD457380EAB
#define __INCLUDE_GUARD_8B7841BB_FFCF_42B9_BF24_2CD457380EAB
#ifdef _MSC_VER
#pragma once
#endif

#include "core/algebra.h"
#include "rt/basic_definitions.h"

//A perspective camera implementation
class PerspectiveCamera: public Camera {
	Point m_center;
	Vector m_topLeft;
	Vector m_stepX, m_stepY;

	void init(const Point &_center, const Vector &_forward, const Vector &_up,
			float _vertOpeningAngInGrad, std::pair<uint, uint> _resolution) {
		m_center = _center;

		float aspect_ratio = (float) _resolution.first
				/ (float) _resolution.second;

		Vector forward_axis = ~_forward;
		Vector right_axis = ~(forward_axis % _up);
		Vector up_axis = -~(right_axis % forward_axis);

		float angleInRad = _vertOpeningAngInGrad * (float) M_PI / 180.f;
		Vector row_vector = 2.f * right_axis * tanf(angleInRad / 2.f)
				* aspect_ratio;
		Vector col_vector = 2.f * up_axis * tanf(angleInRad / 2.f);

		m_stepX = row_vector / (float) _resolution.first;
		m_stepY = col_vector / (float) _resolution.second;
		m_topLeft = forward_axis - row_vector / 2.f - col_vector / 2.f;

	}

public:
	PerspectiveCamera(const Point &_center, const Vector &_forward,
			const Vector &_up, float _vertOpeningAngInGrad, std::pair<uint,
					uint> _resolution) {
		init(_center, _forward, _up, _vertOpeningAngInGrad, _resolution);
	}

	PerspectiveCamera(const Point &_center, const Point &_lookAt,
			const Vector &_up, float _vertOpeningAngInGrad, std::pair<uint,
					uint> _resolution) {
		init(_center, _lookAt - _center, _up, _vertOpeningAngInGrad,
				_resolution);
	}

	virtual Ray getPrimaryRay(float _x, float _y) {
		Ray ret;
		ret.o = m_center;
		ret.d = 4.f * (m_topLeft + _x * m_stepX + _y * m_stepY);

		return ret;
	}
};
class ThinLensCamera : public PerspectiveCamera
{
protected:

	float vp_distance;
	Vector forward;
	Vector up;
	float2 circle_confusion_sampler;

public:
	float fp_distance;
	float lens_radius;
	float focal_stop;

	ThinLensCamera(const Point &_center, const Vector &_forward, const Vector &_up,
		float _vertOpeningAngInGrad, std::pair<uint, uint> _resolution)
		: PerspectiveCamera(_center, _forward, _up, _vertOpeningAngInGrad, _resolution)
	{
		init(_center, _forward, _up, _vertOpeningAngInGrad, _resolution);
		vp_distance = (float)_resolution.second / (float)(2 * tan(_vertOpeningAngInGrad * M_PI / 180));
		forward = _forward;
		up = _up;
	}

	ThinLensCamera(const Point &_center, const Point &_lookAt, const Vector &_up,
		float _vertOpeningAngInGrad, std::pair<uint, uint> _resolution)
		: PerspectiveCamera(_center, _lookAt, _up, _vertOpeningAngInGrad, _resolution)
	{
		init(_center, _lookAt - _center, _up, _vertOpeningAngInGrad,_resolution);
		vp_distance = (float)_resolution.second / (float)(2 * tan(_vertOpeningAngInGrad * M_PI / 180));
		forward = _lookAt-_center;
		up = _up;
	}

	virtual void init_sampler()
	{
		float theta = (float) rand() / (float) RAND_MAX * 2 * M_PI;
		float radius = (float) rand() / (float) RAND_MAX;

		circle_confusion_sampler.x = cos(theta) * radius;
		circle_confusion_sampler.y = sin(theta) * radius;
	}

	virtual Ray getPrimaryRay(float _x, float _y)
	{
		lens_radius = fp_distance / focal_stop;
		Ray straightRay;
		straightRay.o = m_center;
		straightRay.d = 4.f * (m_topLeft + _x * m_stepX + _y * m_stepY);

		// Compute hitpoint on focal plane
		Vector hit_plane_point = Vector(straightRay.o.x,straightRay.o.y, straightRay.o.z) + fp_distance * (~straightRay.d);

		// Compute point l on circle of confusion, the coordinate is on the viewplane
		init_sampler();
		circle_confusion_sampler.x *= lens_radius;
		circle_confusion_sampler.y *= lens_radius;

		Vector u = ~(forward % up) * circle_confusion_sampler.x;
		Vector v = ~up * circle_confusion_sampler.y;
		Vector sample_on_circle = u + v + Vector(straightRay.o.x, straightRay.o.y, straightRay.o.z);
		Point newO = Point(sample_on_circle.x, sample_on_circle.y, sample_on_circle.z);
		Vector newDir = Point(hit_plane_point.x, hit_plane_point.y, hit_plane_point.z) - newO;
		Ray ray(newO, newDir);
		return ray;
	}
};

#endif //__INCLUDE_GUARD_8B7841BB_FFCF_42B9_BF24_2CD457380EAB
